home *** CD-ROM | disk | FTP | other *** search
/ Floppyshop 2 / Floppyshop - 2.zip / Floppyshop - 2.iso / diskmags / 5791-.end / dmg-6143 / lza_rept / compres1.txt next >
Text File  |  1995-05-01  |  17KB  |  324 lines

  1.                  Experiments in Data Compression
  2.                          by John Kormylo
  3.                          E.L.F. Software
  4.  
  5.  
  6. Introduction
  7.  
  8.      When first developed,  LZA compression was superior to  that 
  9. obtained  using ZIP or LZH.   Since then,  both ZIP and LZH  have 
  10. improved their compression efficiency dramatically.   This report 
  11. documents attempts to improve LZA to regain its advantage.  While 
  12. it  is possible to out-perform ZIP  consistently,  the  resulting 
  13. algorithm is glacially slow.
  14.  
  15.  
  16. LZA Description
  17.  
  18.      The  LZA  algorithm  is  a  variant  of  LZSS  and  adaptive 
  19. arithmetic (see "The Data Compression Book" by Mark Nelson).   It 
  20. differs  from standard adaptive arithmetic by using a "new  code" 
  21. character.   It  differs from LZSS by using additional  codes  to 
  22. transmit match tokens.
  23.      The  "new  code"  character is followed  by  a  (relatively) 
  24. uncompressed character which had previously been assigned a count 
  25. of zero.  Adaptive arithmetic normally assigns a minimum count of 
  26. one  to all 256 characters,  which makes the  coding  inefficient 
  27. when  only a few characters are used  (e.g.,  text  files).   The 
  28. overhead  for  entering the character set using  the  "new  code" 
  29. character  is comparable to the overhead for  transmitting  count 
  30. information for a non-adaptive method. 
  31.      The  "new code" character always has a count of  one  (until 
  32. no  new codes are left.)  The new codes are entered  by  dividing 
  33. the  current range (high - low) into 'n' segments of equal  size, 
  34. where  'n' is the number of remaining characters with a count  of 
  35. zero.
  36.  
  37.      LZSS adds an extra bit before every character or match token 
  38. to  indicate which is being used.   LZA introduces an  additional 
  39. character  which is followed by the match token.   If  there  are 
  40. exactly the same number of tokens as characters transmitted, then 
  41. this  additional character will require only one bit to be  coded 
  42. and  every other character will require one additional bit to  be 
  43. coded.   For any ratio other than one to one,  using this special 
  44. character is more efficient than using a single bit.
  45.      Actually, LZA adds 64 additional characters corresponding to 
  46. 64 possible match lengths.  This takes advantage of the fact that 
  47. short matches are far more common than long matches by  assigning 
  48. a count to each match length.  Also, since matches longer than 32 
  49. bytes are rare,  assigning an initial count of zero and using the 
  50. "new code" character turns out to be very effective.
  51.      Past  locations  in the message are organized into  a  self-
  52. balancing  binary  search  tree for faster matching.
  53.  
  54.  
  55. LZA Improvements
  56.  
  57.      Not  all  past locations should be included  in  the  binary 
  58. search tree.   Nelson mentioned that it is common to not  include 
  59. those locations skipped over when a match is found for reasons of 
  60. speed.  As it turns out, the probability of matching one of these 
  61. locations  is so low that not including them in the  search  tree 
  62. improves the compression as well.
  63.      This is not the case,  however,  with the location  matched.  
  64. While there is already another location in the search tree  which 
  65. matches  the first few characters,  the probability of finding  a 
  66. longer  match  using the new location is high enough  to  justify 
  67. its inclusion.
  68.      Another  improvement can be made by keeping a count for  how 
  69. often  each location is used.   Rather than dividing the  current 
  70. range into segments of equal size for each location in the search 
  71. tree,  the size of the segments can be made proportional to  this 
  72. count (set to one initially).   Also,  when the tree is full, the 
  73. new  LZA does not throw out old entries with counts greater  than 
  74. one.   Instead  it  decrements the count and allows it  to  cycle 
  75. through again (until the count goes to zero).
  76.      For speed,  LZA keeps a table of search tree indexes  sorted 
  77. by  count  (in decreasing order),  plus an array  containing  the 
  78. number  of  entries with counts 1 through 16.   The  sum  of  all 
  79. counts up to an entry with a count of one is given by the sum of 
  80. all  counts (precomputed) minus the number of entries  after  the 
  81. index  (times one).   This is much faster than summing up to  16K 
  82. numbers.
  83.      Also, if a location is used more than once, odds are that it 
  84. will use the same length as before.  Therefore, one of the length 
  85. codes means "use the same length as last time."  (It replaced the 
  86. length  =  2 code,  which was never used in practice.)   Since  a 
  87. location  must  have  been  used before for  this  length  to  be 
  88. defined,  only those search tree entries with counts greater than 
  89. one are used when computing the index code.
  90.      While  searching through the binary tree will easily  locate 
  91. the longest match,  it will not necessarily locate the best match 
  92. of that length (the one with the highest count).   Any match with 
  93. the same length as the one found will be in the sub-tree starting 
  94. with the entry found,  but one must search both branches to  find 
  95. all  of  them.   This can be done using a recursive  function  to 
  96. search both branches whenever a duplicate match is found.
  97.  
  98.  
  99. Tree Size Experiments
  100.  
  101.      Text  files  generally do best with a very large  tree  (16K 
  102. entries  or  more).   Programs do better with  trees  with  fewer 
  103. entries.  A sorted text file (actually, a dictionary) worked best 
  104. with a 16 entry search tree.
  105.      The following table shows the compressed file size for three 
  106. test  datasets as a function of tree size.   For  comparison  the 
  107. corresponding  ZIP compressed file sizes are included.   (Two  of 
  108. these  files can be downloaded from Genie,  and one is  from  the 
  109. Atari developer's package.)
  110.      One  way  to improve performance over a wide range  of  tree 
  111. sizes  is to use more than one tree at a time and include a  code 
  112. to select which tree to use.   Best results were obtained using 3 
  113. trees:  sizes 16,  512 & 16K.   These results are included in the 
  114. following table (as "3 trees").
  115.      Actually,  there are many ways to do this.  One way would be 
  116. to use a different group of length codes for each tree.   Another 
  117. would be to use regular codes to indicate the tree selected and a 
  118. separate code set (like the index code) for the length indicator.  
  119. However best results were obtained using the same length codes as 
  120. before  and  a  separate  set of codes  for  the  tree  selection 
  121. indicator.
  122.  
  123.  
  124.   tree | DEMONSTR.DOC | ELFBACK.PRG | WORDLIST.TXT |
  125. -------+--------------+-------------+--------------+
  126.     16 |       45055  |      46100  |  >   476285  |
  127.     32 |       44186  |      43144  |      488860  |
  128.     64 |       40211  |      41018  |      506394  |
  129.    128 |       37345  |      38961  |      521958  |
  130.    256 |       34163  |      36954  |      539121  |
  131.    512 |       31198  |      35443  |      559172  |
  132.   1024 |       28756  |      34483  |      582964  |
  133.   2048 |       27039  |      33763  |      612184  |
  134.   4096 |       25765  |  >   33673  |      639819  |
  135.   8192 |       25114  |      33822  |      658753  |
  136.  16384 |  >    24942  |      34003  |      670774  |
  137. -------+--------------+-------------+--------------+
  138.    ZIP |       25207  |      32444  |      496880  |
  139. -------+--------------+-------------+--------------+
  140. 3 trees|       24928  |      33073  |      559810  |
  141.   avg  |       24638  |      33139  |      556216  |
  142. -------+--------------+-------------+--------------+
  143.   gaps |       28202  |      33867  |      471766  |
  144.   trim |       24910  |      33163  |      531371  |
  145.   best |       22828  |      32191  |      457176  |
  146.  
  147.  
  148.      It is interesting to note that this method out-performed the 
  149. best  single tree size for two of the  three  files.   Also,  the 
  150. resulting   counts  showed  that  all  three  trees   were   used 
  151. frequently,  and that the smaller trees were used far more  often 
  152. than would be indicated by their relative sizes.
  153.      Up to this point,  comparisons between matches of  different 
  154. lengths were made by adding the cost of the missing characters to 
  155. the  shorter of the two matches.   However,  it is possible  that 
  156. more  than  one  short match could  replace  a  long  match.   To 
  157. overcome  this  problem,  one can compare the  average  cost  per 
  158. character for the two matches.   Specifically,  this is  computed 
  159. using
  160.               avg = log(4294967296.0 / size) / len
  161.  
  162. where 'len' is the number of characters and 'size' is the segment 
  163. size   (starting  from  4294967296)  for   resulting   arithmetic 
  164. compression for the length,  tree,  and index codes.
  165.      This  not only works well for selecting which tree  to  use, 
  166. but also whether to use a match at all (by comparing the  average 
  167. cost  per  character to the cost of the  first  character).   The 
  168. results are shown in the first table (as 'avg').
  169.  
  170.      The most surprizing thing is that this approach performed so 
  171. poorly  on WORDLIST.TXT relative to how it performed  using  only 
  172. the 16 entry tree.   A test was run which used the 16 entry  tree 
  173. wherever  possible,  and the other trees only when no  match  was 
  174. found.  The resulting compressed dataset was 556869 bytes long.
  175.      It  was  also discovered that the 'avg' method used  the  16 
  176. entry tree 93278 times, the "use 16 if possible" test used the 16 
  177. entry tree 99475 times,  but using the 16 tree only found  164708 
  178. matches.   These  matches must have come from INSIDE the  matches 
  179. found  in the 512 and 16K entry trees.   Obviously,  no  "greedy" 
  180. algorithm was going to find those matches.
  181.      Another test was run which only looked for matches from  the 
  182. large  search trees to fill in the gaps between matches with  the 
  183. small  trees.   The  results  are shown in the  first  table  (as 
  184. 'gaps').   While this worked well for WORDLIST.TXT,  it performed 
  185. very poorly on DEMONSTR.DOC.
  186.      A  similar test was run which used shorter matches when  the 
  187. longest  match found extended past the start of a match from  the 
  188. 16  entry  tree.   The results are shown in the first  table  (as 
  189. 'trim').   This  performed well on all test files.
  190.      While these tests were not good enough,  they did show  that 
  191. using  three  trees could give acceptable results if  the  proper 
  192. matches were used.   However,  to find these matches it would  be 
  193. necessary  to test all possible ways to compress the  data.   The 
  194. algorithm tried would check all possible combinations of  matches 
  195. and  characters until a unique first step was found,  32 bits  of 
  196. compressed output was produced,  or 256 bytes of input data  were 
  197. compressed  (whichever  came first).   After the first  step  was 
  198. implemented,  the  process was started all over again  since  the 
  199. counts  and search trees had been modified and the  old  analysis 
  200. was  no longer valid.   The results are shown in the first  table 
  201. (as  'best').   The only problem with this method is that  it  is 
  202. VERY slow.
  203.  
  204.  
  205. Finding Contiguous Matches
  206.  
  207.      In  this  section  we  will  attempt  to  equal  the  'best' 
  208. algorithm using another approach.
  209.      If  a  match can be found which extends past the  end  of  a 
  210. larger match,  but no match can be found at the end of the larger 
  211. match,  then the location inside the smaller match  corresponding 
  212. to the end of the larger match must have been skipped.  Therefore 
  213. by including the skipped locations in (some) of the search trees, 
  214. one should be able to locate more contiguous matches.
  215.      The following table shows the compressed file sizes using  a 
  216. single  search tree which includes all locations  (no  skipping).  
  217. These do not perform nearly as well as the corresponding trees in 
  218. the first table.
  219.      If  we  combine  the 3 trees  as  before,  the  results  are 
  220. surprisingly  respectable.   The '0,3' values use no skipping  in 
  221. all three trees (16,  512, & 16K).  The '1,2' values use skipping 
  222. in the 16K entry tree but not in the other two.  The '2,1' values 
  223. use skipping in all but the 16 entry tree.
  224.  
  225.   tree | DEMONSTR.DOC | ELFBACK.PRG | WORDLIST.TXT |
  226. -------+--------------+-------------+--------------+
  227.     16 |       45334  |      47560  |      623420  |
  228.    512 |       39661  |      40590  |      620509  |
  229.  16384 |       34762  |      37505  |      696476  |
  230. -------+--------------+-------------+--------------+
  231.   0,3  |       25401  |      32829  |      516093  |
  232.   1,2  |       24570  |      33000  |      514302  |
  233.   2,1  |       24641  |      33163  |      511917  |
  234. -------+--------------+-------------+--------------+
  235.   3,1  |       24612  |      33094  |      507442  |
  236.   3,2  |       24457  |      32843  |      504815  |
  237. -------+--------------+-------------+--------------+
  238.  mixed |       24261  |      32854  |      503976  |
  239.  
  240.  
  241.      Even  better results can be obtained using additional  trees 
  242. which contain only the (previously) skipped locations.  The '3,1' 
  243. values  use  the  previous  3 trees  (16,  512  &  16K)  plus  an 
  244. additional  16 entry tree which contains ONLY skipped  locations.  
  245. The  '3,2' values use the same plus an additional 512 entry  tree 
  246. which also contains ONLY skipped locations.
  247.      The  best  results for this approach were obtained  using  a 
  248. '3,2' tree combination as before, but now the first 16 entry tree 
  249. contains  ONLY  the matched locations while the second  16  entry 
  250. tree  contains  ALL locations.   These results are shown  in  the 
  251. above table (as 'mixed').
  252.      
  253.  
  254. Biting the Bullet
  255.  
  256.      While the results in Table 2 are interesting,  the only  way 
  257. to  consistently  out-perform  ZIP  is to  use  the  full  search 
  258. ('best')  algorithm.   In this section we will be concerned  with 
  259. improving this approach.
  260.      There  are  several reasons why this approach is  more  time 
  261. consuming.   First, it must search for a match at every location.  
  262. Second,  it  must  search for the best match  for  each  possible 
  263. length.   The  time  required to perform this search  is  roughly 
  264. proportional  to how many matches are found.   Finally,  it  must 
  265. repeat some of these searches,  since the results are thrown away 
  266. once the next code is determined.
  267.      In  the original algorithm,  the limiting factor was  almost 
  268. always  the  32 bits of output.   In fact,  one  can  reduce  the 
  269. maximum  number  of  input bytes to 65 with no  penalty  at  all.  
  270. Further  reduction  requires  also  reducing  the  maximum  match 
  271. length.   The  results for various length parameters in shown  in 
  272. the next table.  However, the resulting increase in speed was not 
  273. substantial.
  274.  
  275. length | DEMONSTR.DOC | ELFBACK.PRG | WORDLIST.TXT |
  276. -------+--------------+-------------+--------------+
  277.     65 |       22828  |      32191  |      457176  |
  278.     33 |       22928  |      32261  |      457174  |
  279.     17 |       23794  |      32726  |      455700  |
  280.      9 |       26552  |      34073  |      484471  |
  281.  
  282.      On the other hand,  by increasing the number of output  bits 
  283. (actually,  by  computing  segments  sizes  with  floating  point 
  284. numbers   instead  of  unsigned  longs)  one  can   improve   the 
  285. compression, as shown in the next table.  At least using floating 
  286. point operations does not significanly slow it down any  further.  
  287. The  limiting  factor now becomes the number of tests  needed  to 
  288. determine  a unique first step.   This is usually  only  slightly 
  289. larger than the initial match length.
  290.  
  291.  
  292. length | DEMONSTR.DOC | ELFBACK.PRG | WORDLIST.TXT |
  293. -------+--------------+-------------+--------------+
  294.     65 |       22196  |      32035  |      455017  |
  295.     33 |       22329  |      32137  |      455261  |
  296. -------+--------------+-------------+--------------+
  297.    len |       22522  |      32054  |      461290  |
  298.  first |       22595  |      32067  |      479614  |
  299.   save |       22170  |      32037  |      456498  |
  300.  
  301.  
  302.      One way to reduce the number of match searches is to  assume 
  303. that  a match taken from a smaller tree is always  preferable  to 
  304. the same length match taken from a larger tree.   This turns  out 
  305. not  to be the case,  however,  as shown in the above  table  (as 
  306. 'len').  Nor does it run much faster.
  307.      Alternatively,  one  could  eschew the search for  the  best 
  308. match, taking the first match found.  Again, there is little time 
  309. saved  at the cost of compression efficiency.   The  results  are 
  310. shown in the above table (as 'first').
  311.      Another  possible way to improve speed would be to save  the 
  312. results after each step (as opposed to starting all over  again).  
  313. This would mean far fewer searches.   However,  it would  require 
  314. performing  all of the search tree updates and sorts  and  saving 
  315. enough information to undo them.   Because of all the  additional 
  316. tree and sort operations,  this approach is only slightly  faster 
  317. than the 'best' algorithm.  The good news is that the performance 
  318. is about the same, as shown in the above table (as 'save').
  319.      Since LZW is inherently faster than LZSS, attempts were made 
  320. to use LZW variants together with the full search.   However, not 
  321. only  were  the  results  not even  in  the  same  ballpark,  the 
  322. algorithm speed still left a lot to be desired.
  323.  
  324.